package txscript

import (
	"encoding/binary"
)

type Script struct {
	stack []OP
}

func NewScriptFromBytes(raw []byte) *Script {
	ps := new(Script)
	ps.stack = []OP{}
	ps.readOpcode(raw)
	return ps
}

func NewEmptyScript() *Script {
	ps := new(Script)
	ps.stack = []OP{}
	return ps
}

func (s *Script) readOpcode(raw []byte) {
	for {
		if len(raw) == 0 {
			break
		}
		opcode := raw[0]
		if opcode >= OP_DATA_1 && opcode <= OP_DATA_75 {
			raw = s.readOpData(raw)
			continue
		}
		if opcode == OP_PUSHDATA1 || opcode == OP_PUSHDATA2 || opcode == OP_PUSHDATA4 {
			raw = s.readOpPush(raw)
			continue
		}
		raw = s.readOpOther(raw)
	}
}

func (s *Script) readOpData(raw []byte) []byte {
	opcode := raw[0]
	length := int(opcode)
	data := raw[1 : 1+length]
	s.stack = append(s.stack, OP{
		code: opcode,
		data: data,
	})
	return raw[1+length:]
}

func (s *Script) readOpPush(raw []byte) []byte {
	opcode := raw[0]
	if opcode == OP_PUSHDATA1 {
		length := uint8(raw[1])
		data := raw[2 : 2+int(length)]
		s.stack = append(s.stack, OP{
			code: opcode,
			data: data,
		})
		return raw[2+int(length):]
	}
	if opcode == OP_PUSHDATA2 {
		length := binary.LittleEndian.Uint16(raw[1:])
		data := raw[3 : 3+int(length)]
		s.stack = append(s.stack, OP{
			code: opcode,
			data: data,
		})
		return raw[3+int(length):]
	}
	if opcode == OP_PUSHDATA4 {
		length := binary.LittleEndian.Uint32(raw[1:])
		data := raw[5 : 5+int(length)]
		s.stack = append(s.stack, OP{
			code: opcode,
			data: data,
		})
		return raw[5+int(length):]
	}
	return nil
}

func (s *Script) readOpOther(raw []byte) []byte {
	opcode := raw[0]
	s.stack = append(s.stack, OP{code: opcode})
	return raw[1:]
}

func (s *Script) GetOP(i int) OP {
	return s.stack[i]
}

func (s *Script) PushOP(op OP) {
	s.stack = append(s.stack, op)
}

// pay-to-pubkey transaction
func (s *Script) IsP2PK() bool {
	if len(s.stack) != 2 {
		return false
	}
	if len(s.stack[0].data) == 33 || len(s.stack[0].data) == 65 {
		if s.stack[1].code == OP_CHECKSIG {
			return true
		}
		return false
	}
	return false
}

// pay-to-pubkey-hash
func (s *Script) IsP2PKH() bool {
	if len(s.stack) != 5 {
		return false
	}
	if s.stack[0].code != OP_DUP {
		return false
	}
	if s.stack[1].code != OP_HASH160 {
		return false
	}
	if s.stack[2].code != OP_DATA_20 {
		return false
	}
	if s.stack[3].code != OP_EQUALVERIFY {
		return false
	}
	if s.stack[4].code != OP_CHECKSIG {
		return false
	}
	return true
}

// pay-to-script-hash
func (s *Script) IsP2SH() bool {
	if len(s.stack) != 3 {
		return false
	}
	if s.stack[0].code != OP_HASH160 {
		return false
	}
	if s.stack[1].code != OP_DATA_20 {
		return false
	}
	if s.stack[2].code != OP_EQUAL {
		return false
	}
	return true
}

func (s *Script) Bytes() []byte {
	data := []byte{}
	for _, op := range s.stack {
		data = append(data, op.code)
		if len(op.data) > 0 {
			data = append(data, op.data...)
		}
	}
	return data
}

func (s *Script) String() string {
	buf := ""
	for _, item := range s.stack {
		buf = buf + item.String() + "  "
	}
	return buf
}
